/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */
//= ===========================================================
//= ===========================================================
//  class L2DBaseModel
//= ===========================================================
//= ===========================================================
function L2DBaseModel() {
  this.live2DModel = null // ALive2DModel
  this.modelMatrix = null // L2DModelMatrix
  this.eyeBlink = null // L2DEyeBlink
  this.physics = null // L2DPhysics
  this.pose = null // L2DPose
  this.debugMode = false
  this.initialized = false
  this.updating = false
  this.alpha = 1
  this.accAlpha = 0
  this.lipSync = false
  this.lipSyncValue = 0
  this.accelX = 0
  this.accelY = 0
  this.accelZ = 0
  this.dragX = 0
  this.dragY = 0
  this.startTimeMSec = null
  this.mainMotionManager = new L2DMotionManager() // L2DMotionManager
  this.expressionManager = new L2DMotionManager() // L2DMotionManager
  this.motions = {}
  this.expressions = {}

  this.isTexLoaded = false
}

let texCounter = 0

//= ===========================================================
//    L2DBaseModel # getModelMatrix()
//= ===========================================================
L2DBaseModel.prototype.getModelMatrix = function () {
  return this.modelMatrix
}

//= ===========================================================
//    L2DBaseModel # setAlpha()
//= ===========================================================
L2DBaseModel.prototype.setAlpha = function (a /* float */) {
  if (a > 0.999) a = 1
  if (a < 0.001) a = 0
  this.alpha = a
}

//= ===========================================================
//    L2DBaseModel # getAlpha()
//= ===========================================================
L2DBaseModel.prototype.getAlpha = function () {
  return this.alpha
}

//= ===========================================================
//    L2DBaseModel # isInitialized()
//= ===========================================================
L2DBaseModel.prototype.isInitialized = function () {
  return this.initialized
}

//= ===========================================================
//    L2DBaseModel # setInitialized()
//= ===========================================================
L2DBaseModel.prototype.setInitialized = function (v /* boolean */) {
  this.initialized = v
}

//= ===========================================================
//    L2DBaseModel # isUpdating()
//= ===========================================================
L2DBaseModel.prototype.isUpdating = function () {
  return this.updating
}

//= ===========================================================
//    L2DBaseModel # setUpdating()
//= ===========================================================
L2DBaseModel.prototype.setUpdating = function (v /* boolean */) {
  this.updating = v
}

//= ===========================================================
//    L2DBaseModel # getLive2DModel()
//= ===========================================================
L2DBaseModel.prototype.getLive2DModel = function () {
  return this.live2DModel
}

//= ===========================================================
//    L2DBaseModel # setLipSync()
//= ===========================================================
L2DBaseModel.prototype.setLipSync = function (v /* boolean */) {
  this.lipSync = v
}

//= ===========================================================
//    L2DBaseModel # setLipSyncValue()
//= ===========================================================
L2DBaseModel.prototype.setLipSyncValue = function (v /* float */) {
  this.lipSyncValue = v
}

//= ===========================================================
//    L2DBaseModel # setAccel()
//= ===========================================================
L2DBaseModel.prototype.setAccel = function (x /* float */, y /* float */, z /* float */) {
  this.accelX = x
  this.accelY = y
  this.accelZ = z
}

//= ===========================================================
//    L2DBaseModel # setDrag()
//= ===========================================================
L2DBaseModel.prototype.setDrag = function (x /* float */, y /* float */) {
  this.dragX = x
  this.dragY = y
}

//= ===========================================================
//    L2DBaseModel # getMainMotionManager()
//= ===========================================================
L2DBaseModel.prototype.getMainMotionManager = function () {
  return this.mainMotionManager
}

//= ===========================================================
//    L2DBaseModel # getExpressionManager()
//= ===========================================================
L2DBaseModel.prototype.getExpressionManager = function () {
  return this.expressionManager
}

//= ===========================================================
//    L2DBaseModel # loadModelData()
//= ===========================================================
L2DBaseModel.prototype.loadModelData = function (path /* String */, callback) {
  /*
    if( this.live2DModel != null ) {
        this.live2DModel.deleteTextures();
    }
    */
  const pm = Live2DFramework.getPlatformManager() // IPlatformManager
  if (this.debugMode) pm.log('Load model : ' + path)

  const thisRef = this
  pm.loadLive2DModel(path, function (l2dModel) {
    thisRef.live2DModel = l2dModel
    thisRef.live2DModel.saveParam()

    const _err = Live2D.getError()

    if (_err != 0) {
      console.error('Error : Failed to loadModelData().')
      return
    }

    thisRef.modelMatrix = new L2DModelMatrix(
      thisRef.live2DModel.getCanvasWidth(),
      thisRef.live2DModel.getCanvasHeight()
    ) // L2DModelMatrix
    thisRef.modelMatrix.setWidth(2)
    thisRef.modelMatrix.setCenterPosition(0, 0)

    callback(thisRef.live2DModel)
  })
}

//= ===========================================================
//    L2DBaseModel # loadTexture()
//= ===========================================================
L2DBaseModel.prototype.loadTexture = function (no /* int */, path /* String */, callback) {
  texCounter++

  const pm = Live2DFramework.getPlatformManager() // IPlatformManager

  if (this.debugMode) pm.log('Load Texture : ' + path)

  const thisRef = this
  pm.loadTexture(this.live2DModel, no, path, function () {
    texCounter--
    if (texCounter == 0) thisRef.isTexLoaded = true
    if (typeof callback === 'function') callback()
  })
}

//= ===========================================================
//    L2DBaseModel # loadMotion()
//= ===========================================================
L2DBaseModel.prototype.loadMotion = function (name /* String */, path /* String */, callback) {
  const pm = Live2DFramework.getPlatformManager() // IPlatformManager

  if (this.debugMode) pm.log('Load Motion : ' + path)

  let motion = null // Live2DMotion

  const thisRef = this
  pm.loadBytes(path, function (buf) {
    motion = Live2DMotion.loadMotion(buf)
    if (name != null) {
      thisRef.motions[name] = motion
    }
    callback(motion)
  })
}

//= ===========================================================
//    L2DBaseModel # loadExpression()
//= ===========================================================
L2DBaseModel.prototype.loadExpression = function (name /* String */, path /* String */, callback) {
  const pm = Live2DFramework.getPlatformManager() // IPlatformManager

  if (this.debugMode) pm.log('Load Expression : ' + path)

  const thisRef = this
  pm.loadBytes(path, function (buf) {
    if (name != null) {
      thisRef.expressions[name] = L2DExpressionMotion.loadJson(buf)
    }
    if (typeof callback === 'function') callback()
  })
}

//= ===========================================================
//    L2DBaseModel # loadPose()
//= ===========================================================
L2DBaseModel.prototype.loadPose = function (path /* String */, callback) {
  const pm = Live2DFramework.getPlatformManager() // IPlatformManager
  if (this.debugMode) pm.log('Load Pose : ' + path)
  const thisRef = this
  try {
    pm.loadBytes(path, function (buf) {
      thisRef.pose = L2DPose.load(buf)
      if (typeof callback === 'function') callback()
    })
  } catch (e) {
    console.warn(e)
  }
}

//= ===========================================================
//    L2DBaseModel # loadPhysics()
//= ===========================================================
L2DBaseModel.prototype.loadPhysics = function (path /* String */) {
  const pm = Live2DFramework.getPlatformManager() // IPlatformManager
  if (this.debugMode) pm.log('Load Physics : ' + path)
  const thisRef = this
  try {
    pm.loadBytes(path, function (buf) {
      thisRef.physics = L2DPhysics.load(buf)
    })
  } catch (e) {
    console.warn(e)
  }
}

//= ===========================================================
//    L2DBaseModel # hitTestSimple()
//= ===========================================================
L2DBaseModel.prototype.hitTestSimple = function (drawID, testX, testY) {
  const drawIndex = this.live2DModel.getDrawDataIndex(drawID)

  if (drawIndex < 0) return false

  const points = this.live2DModel.getTransformedPoints(drawIndex)
  let left = this.live2DModel.getCanvasWidth()
  let right = 0
  let top = this.live2DModel.getCanvasHeight()
  let bottom = 0

  for (let j = 0; j < points.length; j = j + 2) {
    const x = points[j]
    const y = points[j + 1]

    if (x < left) left = x
    if (x > right) right = x
    if (y < top) top = y
    if (y > bottom) bottom = y
  }
  const tx = this.modelMatrix.invertTransformX(testX)
  const ty = this.modelMatrix.invertTransformY(testY)

  return left <= tx && tx <= right && top <= ty && ty <= bottom
}

/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */

//= ===========================================================
//= ===========================================================
//  class L2DExpressionMotion  extends     AMotion
//= ===========================================================
//= ===========================================================
function L2DExpressionMotion() {
  AMotion.prototype.constructor.call(this)
  this.paramList = new Array() // ArrayList<L2DExpressionParam>
}

L2DExpressionMotion.prototype = new AMotion() // L2DExpressionMotion extends AMotion

//= ===========================================================
L2DExpressionMotion.EXPRESSION_DEFAULT = 'DEFAULT'
L2DExpressionMotion.TYPE_SET = 0
L2DExpressionMotion.TYPE_ADD = 1
L2DExpressionMotion.TYPE_MULT = 2

//= ===========================================================
//    static L2DExpressionMotion.loadJson()
//= ===========================================================
L2DExpressionMotion.loadJson = function (buf) {
  const ret = new L2DExpressionMotion()

  const pm = Live2DFramework.getPlatformManager()
  const json = pm.jsonParseFromBytes(buf)

  ret.setFadeIn(parseInt(json.fade_in) > 0 ? parseInt(json.fade_in) : 1000)
  ret.setFadeOut(parseInt(json.fade_out) > 0 ? parseInt(json.fade_out) : 1000)

  if (json.params == null) {
    return ret
  }

  const params = json.params
  const paramNum = params.length
  ret.paramList = [] // ArrayList<L2DExpressionParam>
  for (let i = 0; i < paramNum; i++) {
    const param = params[i]
    const paramID = param.id.toString()
    let value = parseFloat(param.val)
    let calcTypeInt = L2DExpressionMotion.TYPE_ADD
    const calc = param.calc != null ? param.calc.toString() : 'add'
    if (calc === 'add') {
      calcTypeInt = L2DExpressionMotion.TYPE_ADD
    } else if (calc === 'mult') {
      calcTypeInt = L2DExpressionMotion.TYPE_MULT
    } else if (calc === 'set') {
      calcTypeInt = L2DExpressionMotion.TYPE_SET
    } else {
      calcTypeInt = L2DExpressionMotion.TYPE_ADD
    }
    if (calcTypeInt == L2DExpressionMotion.TYPE_ADD) {
      var defaultValue = param.def == null ? 0 : parseFloat(param.def)
      value = value - defaultValue
    } else if (calcTypeInt == L2DExpressionMotion.TYPE_MULT) {
      var defaultValue = param.def == null ? 1 : parseFloat(param.def)
      if (defaultValue == 0) defaultValue = 1
      value = value / defaultValue
    }

    const item = new L2DExpressionParam()
    item.id = paramID
    item.type = calcTypeInt
    item.value = value

    ret.paramList.push(item)
  }

  return ret
}

//= ===========================================================
//    L2DExpressionMotion # updateParamExe()
//= ===========================================================
L2DExpressionMotion.prototype.updateParamExe = function (
  model /* ALive2DModel */,
  timeMSec /* long */,
  weight /* float */,
  motionQueueEnt /* MotionQueueEnt */
) {
  for (let i = this.paramList.length - 1; i >= 0; --i) {
    const param = this.paramList[i] // L2DExpressionParam
    // if (!param || !param.type) continue;
    if (param.type == L2DExpressionMotion.TYPE_ADD) {
      model.addToParamFloat(param.id, param.value, weight)
    } else if (param.type == L2DExpressionMotion.TYPE_MULT) {
      model.multParamFloat(param.id, param.value, weight)
    } else if (param.type == L2DExpressionMotion.TYPE_SET) {
      model.setParamFloat(param.id, param.value, weight)
    }
  }
}

//= ===========================================================
//= ===========================================================
//  class L2DExpressionParam
//= ===========================================================
//= ===========================================================
function L2DExpressionParam() {
  this.id = ''
  this.type = -1
  this.value = null
}

/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */

//= ===========================================================
//= ===========================================================
//  class L2DEyeBlink
//= ===========================================================
//= ===========================================================
function L2DEyeBlink() {
  this.nextBlinkTime = null /* TODO NOT INIT */ //
  this.stateStartTime = null /* TODO NOT INIT */ //
  this.blinkIntervalMsec = null /* TODO NOT INIT */ //
  this.eyeState = EYE_STATE.STATE_FIRST
  this.blinkIntervalMsec = 4000
  this.closingMotionMsec = 100
  this.closedMotionMsec = 50
  this.openingMotionMsec = 150
  this.closeIfZero = true
  this.eyeID_L = 'PARAM_EYE_L_OPEN'
  this.eyeID_R = 'PARAM_EYE_R_OPEN'
}

//= ===========================================================
//    L2DEyeBlink # calcNextBlink()
//= ===========================================================
L2DEyeBlink.prototype.calcNextBlink = function () {
  const time /* long */ = UtSystem.getUserTimeMSec()
  const r /* Number */ = Math.random()
  return /* (long) */ time + r * (2 * this.blinkIntervalMsec - 1)
}

//= ===========================================================
//    L2DEyeBlink # setInterval()
//= ===========================================================
L2DEyeBlink.prototype.setInterval = function (blinkIntervalMsec /* int */) {
  this.blinkIntervalMsec = blinkIntervalMsec
}

//= ===========================================================
//    L2DEyeBlink # setEyeMotion()
//= ===========================================================
L2DEyeBlink.prototype.setEyeMotion = function (
  closingMotionMsec /* int */,
  closedMotionMsec /* int */,
  openingMotionMsec /* int */
) {
  this.closingMotionMsec = closingMotionMsec
  this.closedMotionMsec = closedMotionMsec
  this.openingMotionMsec = openingMotionMsec
}

//= ===========================================================
//    L2DEyeBlink # updateParam()
//= ===========================================================
L2DEyeBlink.prototype.updateParam = function (model /* ALive2DModel */) {
  const time /*: long */ = UtSystem.getUserTimeMSec()
  let eyeParamValue /*: Number */
  let t /*: Number */ = 0
  switch (this.eyeState) {
    case EYE_STATE.STATE_CLOSING:
      t = (time - this.stateStartTime) / this.closingMotionMsec
      if (t >= 1) {
        t = 1
        this.eyeState = EYE_STATE.STATE_CLOSED
        this.stateStartTime = time
      }
      eyeParamValue = 1 - t
      break
    case EYE_STATE.STATE_CLOSED:
      t = (time - this.stateStartTime) / this.closedMotionMsec
      if (t >= 1) {
        this.eyeState = EYE_STATE.STATE_OPENING
        this.stateStartTime = time
      }
      eyeParamValue = 0
      break
    case EYE_STATE.STATE_OPENING:
      t = (time - this.stateStartTime) / this.openingMotionMsec
      if (t >= 1) {
        t = 1
        this.eyeState = EYE_STATE.STATE_INTERVAL
        this.nextBlinkTime = this.calcNextBlink()
      }
      eyeParamValue = t
      break
    case EYE_STATE.STATE_INTERVAL:
      if (this.nextBlinkTime < time) {
        this.eyeState = EYE_STATE.STATE_CLOSING
        this.stateStartTime = time
      }
      eyeParamValue = 1
      break
    case EYE_STATE.STATE_FIRST:
    default:
      this.eyeState = EYE_STATE.STATE_INTERVAL
      this.nextBlinkTime = this.calcNextBlink()
      eyeParamValue = 1
      break
  }
  if (!this.closeIfZero) eyeParamValue = -eyeParamValue
  model.setParamFloat(this.eyeID_L, eyeParamValue)
  model.setParamFloat(this.eyeID_R, eyeParamValue)
}

//= = enum EYE_STATE ==
var EYE_STATE = function () {}

EYE_STATE.STATE_FIRST = 'STATE_FIRST'
EYE_STATE.STATE_INTERVAL = 'STATE_INTERVAL'
EYE_STATE.STATE_CLOSING = 'STATE_CLOSING'
EYE_STATE.STATE_CLOSED = 'STATE_CLOSED'
EYE_STATE.STATE_OPENING = 'STATE_OPENING'
/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */

//= ===========================================================
//= ===========================================================
//  class L2DMatrix44
//= ===========================================================
//= ===========================================================
function L2DMatrix44() {
  this.tr = new Float32Array(16) //
  this.identity()
}

//= ===========================================================
//    static L2DMatrix44.mul()
//= ===========================================================
L2DMatrix44.mul = function (a /* float[] */, b /* float[] */, dst /* float[] */) {
  const c = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  const n = 4
  let i, j, k
  for (i = 0; i < n; i++) {
    for (j = 0; j < n; j++) {
      for (k = 0; k < n; k++) {
        c[i + j * 4] += a[i + k * 4] * b[k + j * 4]
      }
    }
  }
  for (i = 0; i < 16; i++) {
    dst[i] = c[i]
  }
}

//= ===========================================================
//    L2DMatrix44 # identity()
//= ===========================================================
L2DMatrix44.prototype.identity = function () {
  for (let i /*: int */ = 0; i < 16; i++) this.tr[i] = i % 5 == 0 ? 1 : 0
}

//= ===========================================================
//    L2DMatrix44 # getArray()
//= ===========================================================
L2DMatrix44.prototype.getArray = function () {
  return this.tr
}

//= ===========================================================
//    L2DMatrix44 # getCopyMatrix()
//= ===========================================================
L2DMatrix44.prototype.getCopyMatrix = function () {
  return new Float32Array(this.tr) // this.tr.clone();
}

//= ===========================================================
//    L2DMatrix44 # setMatrix()
//= ===========================================================
L2DMatrix44.prototype.setMatrix = function (tr /* float[] */) {
  if (this.tr == null || this.tr.length != this.tr.length) return
  for (let i /*: int */ = 0; i < 16; i++) this.tr[i] = tr[i]
}

//= ===========================================================
//    L2DMatrix44 # getScaleX()
//= ===========================================================
L2DMatrix44.prototype.getScaleX = function () {
  return this.tr[0]
}

//= ===========================================================
//    L2DMatrix44 # getScaleY()
//= ===========================================================
L2DMatrix44.prototype.getScaleY = function () {
  return this.tr[5]
}

//= ===========================================================
//    L2DMatrix44 # transformX()
//= ===========================================================
L2DMatrix44.prototype.transformX = function (src /* float */) {
  return this.tr[0] * src + this.tr[12]
}

//= ===========================================================
//    L2DMatrix44 # transformY()
//= ===========================================================
L2DMatrix44.prototype.transformY = function (src /* float */) {
  return this.tr[5] * src + this.tr[13]
}

//= ===========================================================
//    L2DMatrix44 # invertTransformX()
//= ===========================================================
L2DMatrix44.prototype.invertTransformX = function (src /* float */) {
  return (src - this.tr[12]) / this.tr[0]
}

//= ===========================================================
//    L2DMatrix44 # invertTransformY()
//= ===========================================================
L2DMatrix44.prototype.invertTransformY = function (src /* float */) {
  return (src - this.tr[13]) / this.tr[5]
}

//= ===========================================================
//    L2DMatrix44 # multTranslate()
//= ===========================================================
L2DMatrix44.prototype.multTranslate = function (shiftX /* float */, shiftY /* float */) {
  const tr1 = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, shiftX, shiftY, 0, 1]
  L2DMatrix44.mul(tr1, this.tr, this.tr)
}

//= ===========================================================
//    L2DMatrix44 # translate()
//= ===========================================================
L2DMatrix44.prototype.translate = function (x /* float */, y /* float */) {
  this.tr[12] = x
  this.tr[13] = y
}

//= ===========================================================
//    L2DMatrix44 # translateX()
//= ===========================================================
L2DMatrix44.prototype.translateX = function (x /* float */) {
  this.tr[12] = x
}

//= ===========================================================
//    L2DMatrix44 # translateY()
//= ===========================================================
L2DMatrix44.prototype.translateY = function (y /* float */) {
  this.tr[13] = y
}

//= ===========================================================
//    L2DMatrix44 # multScale()
//= ===========================================================
L2DMatrix44.prototype.multScale = function (scaleX /* float */, scaleY /* float */) {
  const tr1 = [scaleX, 0, 0, 0, 0, scaleY, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
  L2DMatrix44.mul(tr1, this.tr, this.tr)
}

//= ===========================================================
//    L2DMatrix44 # scale()
//= ===========================================================
L2DMatrix44.prototype.scale = function (scaleX /* float */, scaleY /* float */) {
  this.tr[0] = scaleX
  this.tr[5] = scaleY
}
/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */

//= ===========================================================
//= ===========================================================
//  class L2DModelMatrix       extends     L2DMatrix44
//= ===========================================================
//= ===========================================================
function L2DModelMatrix(w /* float */, h /* float */) {
  L2DMatrix44.prototype.constructor.call(this)
  this.width = w
  this.height = h
}

// L2DModelMatrix extends L2DMatrix44
L2DModelMatrix.prototype = new L2DMatrix44()

//= ===========================================================
//    L2DModelMatrix # setPosition()
//= ===========================================================
L2DModelMatrix.prototype.setPosition = function (x /* float */, y /* float */) {
  this.translate(x, y)
}

//= ===========================================================
//    L2DModelMatrix # setCenterPosition()
//= ===========================================================
L2DModelMatrix.prototype.setCenterPosition = function (x /* float */, y /* float */) {
  const w = this.width * this.getScaleX()
  const h = this.height * this.getScaleY()
  this.translate(x - w / 2, y - h / 2)
}

//= ===========================================================
//    L2DModelMatrix # top()
//= ===========================================================
L2DModelMatrix.prototype.top = function (y /* float */) {
  this.setY(y)
}

//= ===========================================================
//    L2DModelMatrix # bottom()
//= ===========================================================
L2DModelMatrix.prototype.bottom = function (y /* float */) {
  const h = this.height * this.getScaleY()
  this.translateY(y - h)
}

//= ===========================================================
//    L2DModelMatrix # left()
//= ===========================================================
L2DModelMatrix.prototype.left = function (x /* float */) {
  this.setX(x)
}

//= ===========================================================
//    L2DModelMatrix # right()
//= ===========================================================
L2DModelMatrix.prototype.right = function (x /* float */) {
  const w = this.width * this.getScaleX()
  this.translateX(x - w)
}

//= ===========================================================
//    L2DModelMatrix # centerX()
//= ===========================================================
L2DModelMatrix.prototype.centerX = function (x /* float */) {
  const w = this.width * this.getScaleX()
  this.translateX(x - w / 2)
}

//= ===========================================================
//    L2DModelMatrix # centerY()
//= ===========================================================
L2DModelMatrix.prototype.centerY = function (y /* float */) {
  const h = this.height * this.getScaleY()
  this.translateY(y - h / 2)
}

//= ===========================================================
//    L2DModelMatrix # setX()
//= ===========================================================
L2DModelMatrix.prototype.setX = function (x /* float */) {
  this.translateX(x)
}

//= ===========================================================
//    L2DModelMatrix # setY()
//= ===========================================================
L2DModelMatrix.prototype.setY = function (y /* float */) {
  this.translateY(y)
}

//= ===========================================================
//    L2DModelMatrix # setHeight()
//= ===========================================================
L2DModelMatrix.prototype.setHeight = function (h /* float */) {
  const scaleX = h / this.height
  const scaleY = -scaleX
  this.scale(scaleX, scaleY)
}

//= ===========================================================
//    L2DModelMatrix # setWidth()
//= ===========================================================
L2DModelMatrix.prototype.setWidth = function (w /* float */) {
  const scaleX = w / this.width
  const scaleY = -scaleX
  this.scale(scaleX, scaleY)
}

/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */

//= ===========================================================
//= ===========================================================
//  class L2DMotionManager     extends     MotionQueueManager
//= ===========================================================
//= ===========================================================
function L2DMotionManager() {
  MotionQueueManager.prototype.constructor.call(this)
  this.currentPriority = null
  this.reservePriority = null

  this.super = MotionQueueManager.prototype
}

L2DMotionManager.prototype = new MotionQueueManager()

//= ===========================================================
//    L2DMotionManager # getCurrentPriority()
//= ===========================================================
L2DMotionManager.prototype.getCurrentPriority = function () {
  return this.currentPriority
}

//= ===========================================================
//    L2DMotionManager # getReservePriority()
//= ===========================================================
L2DMotionManager.prototype.getReservePriority = function () {
  return this.reservePriority
}

//= ===========================================================
//    L2DMotionManager # reserveMotion()
//= ===========================================================
L2DMotionManager.prototype.reserveMotion = function (priority /* int */) {
  if (this.reservePriority >= priority) {
    return false
  }
  if (this.currentPriority >= priority) {
    return false
  }

  this.reservePriority = priority

  return true
}

//= ===========================================================
//    L2DMotionManager # setReservePriority()
//= ===========================================================
L2DMotionManager.prototype.setReservePriority = function (val /* int */) {
  this.reservePriority = val
}

//= ===========================================================
//    L2DMotionManager # updateParam()
//= ===========================================================
L2DMotionManager.prototype.updateParam = function (model /* ALive2DModel */) {
  const updated = MotionQueueManager.prototype.updateParam.call(this, model)

  if (this.isFinished()) {
    this.currentPriority = 0
  }

  return updated
}

//= ===========================================================
//    L2DMotionManager # startMotionPrio()
//= ===========================================================
L2DMotionManager.prototype.startMotionPrio = function (motion /* AMotion */, priority /* int */) {
  if (priority == this.reservePriority) {
    this.reservePriority = 0
  }
  this.currentPriority = priority
  return this.startMotion(motion, false)
}

/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */

//= ===========================================================
//= ===========================================================
//  class L2DPhysics
//= ===========================================================
//= ===========================================================
function L2DPhysics() {
  this.physicsList = new Array() // ArrayList<PhysicsHair>
  this.startTimeMSec = UtSystem.getUserTimeMSec()
}

//= ===========================================================
//    static L2DPhysics.load()
//= ===========================================================
L2DPhysics.load = function (buf /* byte[] */) {
  const ret = new L2DPhysics() // L2DPhysicsL2DPhysics
  const pm = Live2DFramework.getPlatformManager()
  const json = pm.jsonParseFromBytes(buf)
  const params = json.physics_hair
  const paramNum = params.length
  for (let i = 0; i < paramNum; i++) {
    const param = params[i] // Value
    const physics = new PhysicsHair() // PhysicsHairPhysicsHair
    const setup = param.setup // Value
    const length = parseFloat(setup.length)
    const resist = parseFloat(setup.regist)
    const mass = parseFloat(setup.mass)
    physics.setup(length, resist, mass)
    const srcList = param.src // Value
    const srcNum = srcList.length
    for (var j = 0; j < srcNum; j++) {
      const src = srcList[j] // Value
      var id = src.id // String
      var type = PhysicsHair.Src.SRC_TO_X
      var typeStr = src.ptype // String
      if (typeStr === 'x') {
        type = PhysicsHair.Src.SRC_TO_X
      } else if (typeStr === 'y') {
        type = PhysicsHair.Src.SRC_TO_Y
      } else if (typeStr === 'angle') {
        type = PhysicsHair.Src.SRC_TO_G_ANGLE
      } else {
        UtDebug.error('live2d', 'Invalid parameter:PhysicsHair.Src')
      }
      var scale = parseFloat(src.scale)
      var weight = parseFloat(src.weight)
      physics.addSrcParam(type, id, scale, weight)
    }
    const targetList = param.targets // Value
    const targetNum = targetList.length
    for (var j = 0; j < targetNum; j++) {
      const target = targetList[j] // Value
      var id = target.id // String
      var type = PhysicsHair.Target.TARGET_FROM_ANGLE
      var typeStr = target.ptype // String
      if (typeStr === 'angle') {
        type = PhysicsHair.Target.TARGET_FROM_ANGLE
      } else if (typeStr === 'angle_v') {
        type = PhysicsHair.Target.TARGET_FROM_ANGLE_V
      } else {
        UtDebug.error('live2d', 'Invalid parameter:PhysicsHair.Target')
      }
      var scale = parseFloat(target.scale)
      var weight = parseFloat(target.weight)
      physics.addTargetParam(type, id, scale, weight)
    }
    ret.physicsList.push(physics)
  }
  return ret
}

//= ===========================================================
//    L2DPhysics # updateParam()
//= ===========================================================
L2DPhysics.prototype.updateParam = function (model /* ALive2DModel */) {
  const timeMSec = UtSystem.getUserTimeMSec() - this.startTimeMSec
  for (let i = 0; i < this.physicsList.length; i++) {
    this.physicsList[i].update(model, timeMSec)
  }
}

/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */

//= ===========================================================
//= ===========================================================
//  class L2DPose
//= ===========================================================
//= ===========================================================
function L2DPose() {
  this.lastTime = 0
  this.lastModel = null // ALive2DModel
  this.partsGroups = new Array() // ArrayList<L2DPartsParam[]>
}

//= ===========================================================
//    static L2DPose.load()
//= ===========================================================
L2DPose.load = function (buf /* byte[] */) {
  const ret = new L2DPose() // L2DPose
  const pm = Live2DFramework.getPlatformManager()
  const json = pm.jsonParseFromBytes(buf)
  const poseListInfo = json.parts_visible // Value
  const poseNum = poseListInfo.length
  for (let i_pose = 0; i_pose < poseNum; i_pose++) {
    const poseInfo = poseListInfo[i_pose] // Value
    const idListInfo = poseInfo.group // Value
    const idNum = idListInfo.length
    const partsGroup /* L2DPartsParam */ = new Array()
    for (let i_group = 0; i_group < idNum; i_group++) {
      const partsInfo = idListInfo[i_group] // Value
      const parts = new L2DPartsParam(partsInfo.id) // L2DPartsParamL2DPartsParam
      partsGroup[i_group] = parts
      if (partsInfo.link == null) continue
      const linkListInfo = partsInfo.link // Value
      const linkNum = linkListInfo.length
      parts.link = new Array() // ArrayList<L2DPartsParam>
      for (let i_link = 0; i_link < linkNum; i_link++) {
        const linkParts = new L2DPartsParam(linkListInfo[i_link]) // L2DPartsParamL2DPartsParam
        parts.link.push(linkParts)
      }
    }
    ret.partsGroups.push(partsGroup)
  }

  return ret
}

//= ===========================================================
//    L2DPose # updateParam()
//= ===========================================================
L2DPose.prototype.updateParam = function (model /* ALive2DModel */) {
  if (model == null) return

  if (!(model == this.lastModel)) {
    this.initParam(model)
  }
  this.lastModel = model

  const curTime = UtSystem.getUserTimeMSec()
  let deltaTimeSec = this.lastTime == 0 ? 0 : (curTime - this.lastTime) / 1000.0
  this.lastTime = curTime
  if (deltaTimeSec < 0) deltaTimeSec = 0
  for (let i = 0; i < this.partsGroups.length; i++) {
    this.normalizePartsOpacityGroup(model, this.partsGroups[i], deltaTimeSec)
    this.copyOpacityOtherParts(model, this.partsGroups[i])
  }
}

//= ===========================================================
//    L2DPose # initParam()
//= ===========================================================
L2DPose.prototype.initParam = function (model /* ALive2DModel */) {
  if (model == null) return
  for (let i = 0; i < this.partsGroups.length; i++) {
    const partsGroup = this.partsGroups[i] // L2DPartsParam
    for (let j = 0; j < partsGroup.length; j++) {
      partsGroup[j].initIndex(model)
      const partsIndex = partsGroup[j].partsIndex
      const paramIndex = partsGroup[j].paramIndex
      if (partsIndex < 0) continue
      const v /*: Boolean */ = model.getParamFloat(paramIndex) != 0
      model.setPartsOpacity(partsIndex, v ? 1.0 : 0.0)
      model.setParamFloat(paramIndex, v ? 1.0 : 0.0)
      if (partsGroup[j].link == null) continue
      for (let k = 0; k < partsGroup[j].link.length; k++) {
        partsGroup[j].link[k].initIndex(model)
      }
    }
  }
}

//= ===========================================================
//    L2DPose # normalizePartsOpacityGroup()
//= ===========================================================
L2DPose.prototype.normalizePartsOpacityGroup = function (
  model /* ALive2DModel */,
  partsGroup /* L2DPartsParam[] */,
  deltaTimeSec /* float */
) {
  let visibleParts = -1
  let visibleOpacity = 1.0
  const CLEAR_TIME_SEC = 0.5
  const phi = 0.5
  const maxBackOpacity = 0.15
  for (var i = 0; i < partsGroup.length; i++) {
    var partsIndex = partsGroup[i].partsIndex
    const paramIndex = partsGroup[i].paramIndex
    if (partsIndex < 0) continue
    if (model.getParamFloat(paramIndex) != 0) {
      if (visibleParts >= 0) {
        break
      }
      visibleParts = i
      visibleOpacity = model.getPartsOpacity(partsIndex)
      visibleOpacity += deltaTimeSec / CLEAR_TIME_SEC
      if (visibleOpacity > 1) {
        visibleOpacity = 1
      }
    }
  }
  if (visibleParts < 0) {
    visibleParts = 0
    visibleOpacity = 1
  }
  for (var i = 0; i < partsGroup.length; i++) {
    var partsIndex = partsGroup[i].partsIndex
    if (partsIndex < 0) continue
    if (visibleParts == i) {
      model.setPartsOpacity(partsIndex, visibleOpacity)
    } else {
      let opacity = model.getPartsOpacity(partsIndex)
      var a1
      if (visibleOpacity < phi) {
        a1 = (visibleOpacity * (phi - 1)) / phi + 1
      } else {
        a1 = ((1 - visibleOpacity) * phi) / (1 - phi)
      }
      const backOp = (1 - a1) * (1 - visibleOpacity)
      if (backOp > maxBackOpacity) {
        a1 = 1 - maxBackOpacity / (1 - visibleOpacity)
      }
      if (opacity > a1) {
        opacity = a1
      }
      model.setPartsOpacity(partsIndex, opacity)
    }
  }
}

//= ===========================================================
//    L2DPose # copyOpacityOtherParts()
//= ===========================================================
L2DPose.prototype.copyOpacityOtherParts = function (
  model /* ALive2DModel */,
  partsGroup /* L2DPartsParam[] */
) {
  for (let i_group = 0; i_group < partsGroup.length; i_group++) {
    const partsParam = partsGroup[i_group] // L2DPartsParam
    if (partsParam.link == null) continue
    if (partsParam.partsIndex < 0) continue
    const opacity = model.getPartsOpacity(partsParam.partsIndex)
    for (let i_link = 0; i_link < partsParam.link.length; i_link++) {
      const linkParts = partsParam.link[i_link] // L2DPartsParam
      if (linkParts.partsIndex < 0) continue
      model.setPartsOpacity(linkParts.partsIndex, opacity)
    }
  }
}

//= ===========================================================
//= ===========================================================
//  class L2DPartsParam
//= ===========================================================
//= ===========================================================
function L2DPartsParam(id /* String */) {
  this.paramIndex = -1
  this.partsIndex = -1
  this.link = null // ArrayList<L2DPartsParam>
  this.id = id
}

//= ===========================================================
//    L2DPartsParam # initIndex()
//= ===========================================================
L2DPartsParam.prototype.initIndex = function (model /* ALive2DModel */) {
  this.paramIndex = model.getParamIndex('VISIBLE:' + this.id)
  this.partsIndex = model.getPartsDataIndex(PartsDataID.getID(this.id))
  model.setParamFloat(this.paramIndex, 1)
}
/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */

//= ===========================================================
//= ===========================================================
//  class L2DTargetPoint
//= ===========================================================
//= ===========================================================
function L2DTargetPoint() {
  this.EPSILON = 0.01 // 変化の最小値（この値以下は無視される）
  this.faceTargetX = 0
  this.faceTargetY = 0
  this.faceX = 0
  this.faceY = 0
  this.faceVX = 0
  this.faceVY = 0
  this.lastTimeSec = 0
}

//= ===========================================================
L2DTargetPoint.FRAME_RATE = 30

//= ===========================================================
//    L2DTargetPoint # set()
//= ===========================================================
L2DTargetPoint.prototype.setPoint = function (x /* float */, y /* float */) {
  this.faceTargetX = x
  this.faceTargetY = y
}

//= ===========================================================
//    L2DTargetPoint # getX()
//= ===========================================================
L2DTargetPoint.prototype.getX = function () {
  return this.faceX
}

//= ===========================================================
//    L2DTargetPoint # getY()
//= ===========================================================
L2DTargetPoint.prototype.getY = function () {
  return this.faceY
}

//= ===========================================================
//    L2DTargetPoint # update()
//= ===========================================================
L2DTargetPoint.prototype.update = function () {
  const TIME_TO_MAX_SPEED = 0.15
  const FACE_PARAM_MAX_V = 40.0 / 7.5
  const MAX_V = FACE_PARAM_MAX_V / L2DTargetPoint.FRAME_RATE
  if (this.lastTimeSec == 0) {
    this.lastTimeSec = UtSystem.getUserTimeMSec()
    return
  }
  const curTimeSec = UtSystem.getUserTimeMSec()
  const deltaTimeWeight = ((curTimeSec - this.lastTimeSec) * L2DTargetPoint.FRAME_RATE) / 1000.0
  this.lastTimeSec = curTimeSec
  const FRAME_TO_MAX_SPEED = TIME_TO_MAX_SPEED * L2DTargetPoint.FRAME_RATE
  const MAX_A = (deltaTimeWeight * MAX_V) / FRAME_TO_MAX_SPEED
  const dx = this.faceTargetX - this.faceX
  const dy = this.faceTargetY - this.faceY
  // if(dx == 0 && dy == 0) return;
  if (Math.abs(dx) <= this.EPSILON && Math.abs(dy) <= this.EPSILON) return
  const d = Math.sqrt(dx * dx + dy * dy)
  const vx = (MAX_V * dx) / d
  const vy = (MAX_V * dy) / d
  let ax = vx - this.faceVX
  let ay = vy - this.faceVY
  let a = Math.sqrt(ax * ax + ay * ay)
  if (a < -MAX_A || a > MAX_A) {
    ax *= MAX_A / a
    ay *= MAX_A / a
    a = MAX_A
  }
  this.faceVX += ax
  this.faceVY += ay
  {
    const max_v = 0.5 * (Math.sqrt(MAX_A * MAX_A + 16 * MAX_A * d - 8 * MAX_A * d) - MAX_A)
    const cur_v = Math.sqrt(this.faceVX * this.faceVX + this.faceVY * this.faceVY)
    if (cur_v > max_v) {
      this.faceVX *= max_v / cur_v
      this.faceVY *= max_v / cur_v
    }
  }
  this.faceX += this.faceVX
  this.faceY += this.faceVY
}
/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */

//= ===========================================================
//= ===========================================================
//  class L2DViewMatrix        extends     L2DMatrix44
//= ===========================================================
//= ===========================================================
function L2DViewMatrix() {
  L2DMatrix44.prototype.constructor.call(this)
  this.screenLeft = null
  this.screenRight = null
  this.screenTop = null
  this.screenBottom = null
  this.maxLeft = null
  this.maxRight = null
  this.maxTop = null
  this.maxBottom = null
  this.max = Number.MAX_VALUE
  this.min = 0
}

L2DViewMatrix.prototype = new L2DMatrix44() // L2DViewMatrix extends L2DMatrix44

//= ===========================================================
//    L2DViewMatrix # getMaxScale()
//= ===========================================================
L2DViewMatrix.prototype.getMaxScale = function () {
  return this.max
}

//= ===========================================================
//    L2DViewMatrix # getMinScale()
//= ===========================================================
L2DViewMatrix.prototype.getMinScale = function () {
  return this.min
}

//= ===========================================================
//    L2DViewMatrix # setMaxScale()
//= ===========================================================
L2DViewMatrix.prototype.setMaxScale = function (v /* float */) {
  this.max = v
}

//= ===========================================================
//    L2DViewMatrix # setMinScale()
//= ===========================================================
L2DViewMatrix.prototype.setMinScale = function (v /* float */) {
  this.min = v
}

//= ===========================================================
//    L2DViewMatrix # isMaxScale()
//= ===========================================================
L2DViewMatrix.prototype.isMaxScale = function () {
  return this.getScaleX() == this.max
}

//= ===========================================================
//    L2DViewMatrix # isMinScale()
//= ===========================================================
L2DViewMatrix.prototype.isMinScale = function () {
  return this.getScaleX() == this.min
}

//= ===========================================================
//    L2DViewMatrix # adjustTranslate()
//= ===========================================================
L2DViewMatrix.prototype.adjustTranslate = function (shiftX /* float */, shiftY /* float */) {
  if (this.tr[0] * this.maxLeft + (this.tr[12] + shiftX) > this.screenLeft)
    shiftX = this.screenLeft - this.tr[0] * this.maxLeft - this.tr[12]
  if (this.tr[0] * this.maxRight + (this.tr[12] + shiftX) < this.screenRight)
    shiftX = this.screenRight - this.tr[0] * this.maxRight - this.tr[12]
  if (this.tr[5] * this.maxTop + (this.tr[13] + shiftY) < this.screenTop)
    shiftY = this.screenTop - this.tr[5] * this.maxTop - this.tr[13]
  if (this.tr[5] * this.maxBottom + (this.tr[13] + shiftY) > this.screenBottom)
    shiftY = this.screenBottom - this.tr[5] * this.maxBottom - this.tr[13]

  const tr1 = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, shiftX, shiftY, 0, 1]
  L2DMatrix44.mul(tr1, this.tr, this.tr)
}

//= ===========================================================
//    L2DViewMatrix # adjustScale()
//= ===========================================================
L2DViewMatrix.prototype.adjustScale = function (cx /* float */, cy /* float */, scale /* float */) {
  const targetScale = scale * this.tr[0]
  if (targetScale < this.min) {
    if (this.tr[0] > 0) scale = this.min / this.tr[0]
  } else if (targetScale > this.max) {
    if (this.tr[0] > 0) scale = this.max / this.tr[0]
  }
  const tr1 = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, cx, cy, 0, 1]
  const tr2 = [scale, 0, 0, 0, 0, scale, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
  const tr3 = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -cx, -cy, 0, 1]
  L2DMatrix44.mul(tr3, this.tr, this.tr)
  L2DMatrix44.mul(tr2, this.tr, this.tr)
  L2DMatrix44.mul(tr1, this.tr, this.tr)
}

//= ===========================================================
//    L2DViewMatrix # setScreenRect()
//= ===========================================================
L2DViewMatrix.prototype.setScreenRect = function (
  left /* float */,
  right /* float */,
  bottom /* float */,
  top /* float */
) {
  this.screenLeft = left
  this.screenRight = right
  this.screenTop = top
  this.screenBottom = bottom
}

//= ===========================================================
//    L2DViewMatrix # setMaxScreenRect()
//= ===========================================================
L2DViewMatrix.prototype.setMaxScreenRect = function (
  left /* float */,
  right /* float */,
  bottom /* float */,
  top /* float */
) {
  this.maxLeft = left
  this.maxRight = right
  this.maxTop = top
  this.maxBottom = bottom
}

//= ===========================================================
//    L2DViewMatrix # getScreenLeft()
//= ===========================================================
L2DViewMatrix.prototype.getScreenLeft = function () {
  return this.screenLeft
}

//= ===========================================================
//    L2DViewMatrix # getScreenRight()
//= ===========================================================
L2DViewMatrix.prototype.getScreenRight = function () {
  return this.screenRight
}

//= ===========================================================
//    L2DViewMatrix # getScreenBottom()
//= ===========================================================
L2DViewMatrix.prototype.getScreenBottom = function () {
  return this.screenBottom
}

//= ===========================================================
//    L2DViewMatrix # getScreenTop()
//= ===========================================================
L2DViewMatrix.prototype.getScreenTop = function () {
  return this.screenTop
}

//= ===========================================================
//    L2DViewMatrix # getMaxLeft()
//= ===========================================================
L2DViewMatrix.prototype.getMaxLeft = function () {
  return this.maxLeft
}

//= ===========================================================
//    L2DViewMatrix # getMaxRight()
//= ===========================================================
L2DViewMatrix.prototype.getMaxRight = function () {
  return this.maxRight
}

//= ===========================================================
//    L2DViewMatrix # getMaxBottom()
//= ===========================================================
L2DViewMatrix.prototype.getMaxBottom = function () {
  return this.maxBottom
}

//= ===========================================================
//    L2DViewMatrix # getMaxTop()
//= ===========================================================
L2DViewMatrix.prototype.getMaxTop = function () {
  return this.maxTop
}

/**
 *
 *  You can modify and use this source freely
 *  only for the development of application related Live2D.
 *
 *  (c) Live2D Inc. All rights reserved.
 */

//= ===========================================================
//= ===========================================================
//  class Live2DFramework
//= ===========================================================
//= ===========================================================
function Live2DFramework() {}

//= ===========================================================
Live2DFramework.platformManager = null

//= ===========================================================
//    static Live2DFramework.getPlatformManager()
//= ===========================================================
Live2DFramework.getPlatformManager = function () {
  return Live2DFramework.platformManager
}

//= ===========================================================
//    static Live2DFramework.setPlatformManager()
//= ===========================================================
Live2DFramework.setPlatformManager = function (platformManager /* IPlatformManager */) {
  Live2DFramework.platformManager = platformManager
}
